package project2;

/* Alice Robinson and Holly Beach
 * TCSS491 - Winter 2012
 * 
 * Credit for spaceship image goes to
 * http://a1star.com/star-space-animated-aliens.php
 * 
 * Credit for explosion sound effect goes to
 * http://www.mediacollege.com/downloads/sound-effects/explosion/
 */


import java.awt.*;
import java.awt.event.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

import javax.media.opengl.*;
import javax.media.opengl.awt.*;
import javax.media.opengl.glu.*;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.*;

import com.jogamp.common.nio.*;

/**
 * gWorldMain contains the main method for launching a Computational World that
 * includes physics.
 */
public class GWorldMain implements GLEventListener, ActionListener {
	public static GPeer peer;
	private static final int TARGET_FPS = 30;
	private static JFrame appFrame;
	// private static RenderArea my_display;

	private static SceneGraphNode sceneGraphRoot;
	private static boolean pickNextFrame;
	private static Point pickedPoint;
	private static double left, right, top, bottom;
	private static ArrayList<PhysicsObject> objects;

	HalfSpace a_halfspace = new HalfSpace(new Vector2f(-6, 0), new Vector2f(1,
			0), HalfSpace.LEFT_BOUNDARY);

	float r = .55f;
	float g = .28f;
	float b = .15f;

	private int livesLost = 0;
	private int currentScore = 0;
	private int highScore = 0;
	private int nukes = 3;
	String the_message = "<<Player X has joined the game, don't include them in your in-jokes.>>   ";
	JLabel my_score_keeper = new JLabel(the_message
			+ "  Press [space bar] to glass some asteroids.  "
			+ "Current Score: " + currentScore + "  Number of lives lost: "
			+ livesLost + "  High Score: " + highScore + "  Nukes remaining: "
			+ nukes);

	java.util.List<SceneGraphNode> the_picked;

	private Timer my_timer = new Timer(100, this);
	private Timer spawn_timer = new Timer(5000, this);
	private Timer hover_timer = new Timer(3000, this);

	Random rn = new Random();

	public static void main(String[] args) {
		establishP2PCommunication();
		
		GLProfile.initSingleton();

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				(new GWorldMain()).createAndShowGUI();
			}
		});
		
		displayNetworkOptions();
	}

	private void createAndShowGUI() {
		
		
		
		
		// Fix for background flickering
		// System.setProperty("sun.awt.noerasebackground", "true");

		GLProfile profile = GLProfile.getDefault();
		GLCapabilities capabilities = new GLCapabilities(profile);
		// GLCanvas canvas = new GLCanvas(capabilities);
		GLJPanel canvas = new GLJPanel(capabilities);
		canvas.addGLEventListener(this);

		// add physics objects/shapes
		objects = new ArrayList<PhysicsObject>();
		sceneGraphRoot = new SceneGraphNode(false);
		/*
		 * GPhysicsObject obj;
		 * 
		 * //circles float mass = 2f; //float mass = 5f; obj = new GCircle(.5f,
		 * true, .55f,.28f,.15f); obj.inverseMass = 1 / mass; //obj.inverseMass
		 * = (float) (1f/.25); obj.inverseMomentOfInertia *= obj.inverseMass;
		 * obj.position.x = -4f; obj.position.y = 3f; obj.velocity.x = -.5f;
		 * obj.acceleration.y = -4f; attachObject(obj);
		 * 
		 * obj = new GCircle(.5f, true, .55f,.28f,.15f); obj.inverseMass = 1 /
		 * mass; //obj.inverseMass = (float) (1f/.25);
		 * obj.inverseMomentOfInertia *= obj.inverseMass; obj.position.x = 0f;
		 * obj.position.y = 3f; obj.velocity.x = 0f; obj.acceleration.y = -4f;
		 * attachObject(obj);
		 * 
		 * obj = new GCircle(.5f, true, .55f,.28f,.15f); obj.inverseMass = 1 /
		 * mass; //obj.inverseMass = (float) (1f/.25);
		 * obj.inverseMomentOfInertia *= obj.inverseMass; obj.position.x = 4f;
		 * obj.position.y = 3f; obj.velocity.x = .51f; obj.acceleration.y = -4f;
		 * attachObject(obj);
		 */
		/*
		 * //Triangle obj = new GTriangle(1, true, 0,0,1); obj.inverseMass = 1f
		 * / 2; obj.inverseMomentOfInertia *= obj.inverseMass; obj.position.y =
		 * 1; obj.velocity.x = 1f; obj.acceleration.y = -5f; attachObject(obj);
		 * 
		 * obj = new GTriangle(1, true, 0,0,1); obj.inverseMass = 1f / 2;
		 * obj.inverseMomentOfInertia *= obj.inverseMass; obj.position.y = 1;
		 * obj.position.x = -3; obj.velocity.x = .5f; obj.acceleration.y = -3f;
		 * attachObject(obj);
		 */

		// add the boundary
		attachObject(new HalfSpace(new Vector2f(-6, 0), new Vector2f(1, 0), HalfSpace.LEFT_BOUNDARY));
		attachObject(new HalfSpace(new Vector2f(0, -3), new Vector2f(0, 1), HalfSpace.BOTTOM_BOUNDARY));
		attachObject(new HalfSpace(new Vector2f(6, 0), new Vector2f(-1, 0),HalfSpace.RIGHT_BOUNDARY)) ;
		attachObject(new HalfSpace(new Vector2f(0, 3), new Vector2f(0, -1), HalfSpace.TOP_BOUNDARY));
		// Add independent SceneGraphNode for all the HalfSpaces.
		sceneGraphRoot.addChild(new SceneGraphNode(false) {
			public void renderGeometry(GLAutoDrawable drawable) {
				GL2 gl = drawable.getGL().getGL2();
				gl.glColor3f(1, 1, 1);
				gl.glBegin(GL.GL_LINE_LOOP);
				gl.glVertex2f(-6, -3);
				gl.glVertex2f(6, -3);
				gl.glVertex2f(6, 3);
				gl.glVertex2f(-6, 3);

				// gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
				gl.glEnd();
			}
		});

		// Title bar
		String[] my_titles = new String[10];
		my_titles[0] = "Now 20% cooler!";
		my_titles[1] = "The fun has been doubled!";
		my_titles[2] = "Playing with shapes in 10 seconds flat.";
		my_titles[3] = "Moonshine gravy.";
		my_titles[4] = "Now contains less arsenic!";
		my_titles[5] = "When life gives you lemons, give the lemons back. I don't want your lemons.";
		my_titles[6] = "Kilroy was here.";
		my_titles[7] = "Now with zero calories!";
		my_titles[8] = "Now you're thinking with java!";
		my_titles[9] = "Straight to the moon.";

		int a_random = rn.nextInt(10);

		appFrame = new JFrame("Asteroid Field - " + my_titles[a_random]);
		Container the_content = appFrame.getContentPane();
		the_content.setBackground(Color.BLACK);
		the_content.setLayout(new GridBagLayout());
		GridBagConstraints gBC = new GridBagConstraints();
		gBC.fill = GridBagConstraints.BOTH;

		appFrame.setBackground(Color.BLACK);
		// the_content.setVisible(true);

		JPanel my_score_card = new JPanel();

		my_score_card.add(my_score_keeper);
		appFrame.add(my_score_card);

		gBC.weighty = 50;
		gBC.weightx = 0.1;
		gBC.gridx = 0;
		gBC.gridy = 1;
		appFrame.add(canvas, gBC);

		appFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		appFrame.setMinimumSize(new Dimension(256, 256));
		// appFrame.add(canvas, gBC);

		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Image image = toolkit.getImage("src/ufo.png");
		Point hotSpot = new Point(0, 0);
		Cursor cursor = toolkit.createCustomCursor(image, hotSpot, "UFO");
		appFrame.setCursor(cursor);

		KeyboardFocusManager manager = KeyboardFocusManager
				.getCurrentKeyboardFocusManager();
		manager.addKeyEventDispatcher(new MyDispatcher());

		appFrame.pack();
		if (Toolkit.getDefaultToolkit().isFrameStateSupported(
				JFrame.MAXIMIZED_BOTH))
			appFrame.setExtendedState(appFrame.getExtendedState()
					| JFrame.MAXIMIZED_BOTH);
		appFrame.setVisible(true);

		playSound("hover.wav");
		my_timer.start();
		spawn_timer.start();
		hover_timer.start();
	}

	public static void attachObject(PhysicsObject object) {
		if (object.renderable != null)
			sceneGraphRoot.addChild(object.renderable);
		objects.add(object);
	}

	public static void detachObject(PhysicsObject object) {
		if (object.renderable != null)
			sceneGraphRoot.removeChild(object.renderable);
		int index = objects.indexOf(object);
		if (index == -1)
			throw new IllegalArgumentException();
		objects.set(index, objects.get(objects.size() - 1));
		objects.remove(objects.size() - 1);
	}

	public void display(GLAutoDrawable drawable) {

		currentScore++;
		my_score_keeper.setText(the_message
				+ "  Press [space bar] to glass some asteroids.  "
				+ "Current Score: " + currentScore + "  Number of lives lost: "
				+ livesLost + "  High Score: " + highScore
				+ "  Nukes remaining: " + nukes);

		GL2 gl = drawable.getGL().getGL2();
		// gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		appFrame.repaint();

		gl.glClear(GL.GL_COLOR_BUFFER_BIT);

		if (pickNextFrame) {
			GLU glu = GLU.createGLU(gl);
			int viewport[] = new int[4];
			gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
			gl.glMatrixMode(GL2.GL_PROJECTION);
			gl.glPushMatrix();
			gl.glLoadIdentity();
			glu.gluPickMatrix(pickedPoint.x,
					(double) (viewport[3] - pickedPoint.y), 1, 1, viewport, 0);
			gl.glOrtho(left, right, bottom, top, -1, 1);
			gl.glMatrixMode(GL2.GL_MODELVIEW);
			// System.out.println(sceneGraphRoot.getPicked(drawable));

			// GCircle blah = new GCircle(2);

			if (sceneGraphRoot.getPicked(drawable).isEmpty() == false) {
				the_picked = sceneGraphRoot.getPicked(drawable);
				if (the_picked != null) {

					if (currentScore > highScore) {
						highScore = currentScore;
					}

					currentScore = 0;

					livesLost++;
					the_message = "<<Player X prefers the grave.>>   ";

					my_score_keeper.setText(the_message
							+ "  Press [space bar] to glass some asteroids.  "
							+ "Current Score: " + currentScore
							+ "  Number of lives lost: " + livesLost
							+ "  High Score: " + highScore
							+ "  Nukes remaining: " + nukes);

					playSound("bomb.wav");

					// r = .55f;
					// g = .28f;
					// b = .15f;

					for (int i = 0; i < objects.size(); i++) {
						if (objects.get(i).getClass() != a_halfspace.getClass()) {
							objects.remove(i);
							sceneGraphRoot = new SceneGraphNode(false);

							sceneGraphRoot.addChild(new SceneGraphNode(false) {
								public void renderGeometry(
										GLAutoDrawable drawable) {
									GL2 gl = drawable.getGL().getGL2();
									gl.glColor3f(1, 1, 1);
									gl.glBegin(GL.GL_LINE_LOOP);
									gl.glVertex2f(-6, -3);
									gl.glVertex2f(6, -3);
									gl.glVertex2f(6, 3);
									gl.glVertex2f(-6, 3);

									// gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
									gl.glEnd();
								}
							});

							i = 0;
						}
					}

					// objects.clear();
					// String a_string = new
					// String("YOU LOST THE GAME Due to object: " + the_picked);
					// JOptionPane.showMessageDialog(appFrame,
					// "You lost the game.");
					// System.out.println(a_string);
				}
			}

			gl.glMatrixMode(GL2.GL_PROJECTION);
			gl.glPopMatrix();
			gl.glMatrixMode(GL2.GL_MODELVIEW);
			pickNextFrame = false;

		}

		for (PhysicsObject object : objects) {
			object.updateState(1f / TARGET_FPS);
		}
		boolean noCollisions = false;
		for (int repeat = 0; repeat < 10 && !noCollisions; repeat++) {
			noCollisions = true;
			for (int i = 0; i < objects.size(); i++) {
				PhysicsObject a = objects.get(i);
				for (int j = i + 1; j < objects.size(); j++) {
					PhysicsObject b = objects.get(j);
					CollisionInfo cInfo = a.getCollision(b);
					if (cInfo != null) {
						noCollisions = false;
						a.resolveCollision(b, cInfo);
					}
				}
			}
			if (!(repeat + 1 < 10 && !noCollisions)) {
				// System.out.println(repeat + 1);
			}
		}
		for (PhysicsObject object : objects)
			object.updateRenderable();
		sceneGraphRoot.render(drawable);

		// JOptionPane.showMessageDialog(appFrame, "You lost the game.");
	}

	public void dispose(GLAutoDrawable drawable) {
	}

	public void init(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		// gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		gl.glClearColor(0f, 0f, 0f, 0f);
		IntBuffer selectBuffer = Buffers.newDirectIntBuffer(3);
		gl.glSelectBuffer(selectBuffer.capacity(), selectBuffer);
		// FPSAnimator fps = new FPSAnimator(drawable, 30);
		// fps.start();
	}

	public void reshape(GLAutoDrawable drawable, int x, int y, int width,
			int height) {
		final double UNIT = 8;

		if (width < height) {
			left = -UNIT / 2;
			right = UNIT / 2;
			top = (double) height / width * UNIT / 2;
			bottom = -top;
		} else {
			top = UNIT / 2;
			bottom = -UNIT / 2;
			right = (double) width / height * UNIT / 2;
			left = -right;
		}
		GL2 gl = drawable.getGL().getGL2();
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho(left, right, bottom, top, -1, 1);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
	}

	@Override
	public void actionPerformed(ActionEvent the_action) {
		if (the_action.getSource() == my_timer) {
			// System.out.println(Calendar.getInstance().getTime());
			pickNextFrame = true;
			pickedPoint = MouseInfo.getPointerInfo().getLocation();

		} else if (the_action.getSource() == spawn_timer) {
			spawnAsteroids();
		} else if (the_action.getSource() == hover_timer) {
			playSound("hover.wav");
		}
	}

	public void spawnAsteroids() {
/*		PhysicsObject obj;

		//float mass = 2f;
		float size;

			size = rn.nextFloat();
			if (size <= .1f) {
				size = .2f;
			}
			if (size > .6f) {
				size -= .5f;
			}

			obj = new PCircle(size, true, r, g, b);
			//obj.inverseMass = 1 / mass;
			obj.mass = 1.5f;

			//obj.inverseMomentOfInertia *= obj.inverseMass;
			obj.position.x = (float) (rn.nextInt(8) - 4) + rn.nextFloat();
			obj.position.y = 3f;
			//obj.velocity.x = (float) (rn.nextInt(6) - 3) + rn.nextFloat();
			obj.velocity.x = rn.nextFloat();
			obj.acceleration.y = -3f;
			attachObject(obj);
*/


		PhysicsObject obj;
		float size = rn.nextFloat();
		if (size <= .1f) {
			size = .2f;
		}
		if (size > .6f) {
			size -= .5f;
		}
		obj = new PCircle(size, true, r, g, b);
		//obj.velocity.x = .5f;
		float a_velocity = rn.nextInt(3) - 1;
		if(a_velocity < 0) {
			a_velocity += rn.nextFloat();
		} else {
			a_velocity -= rn.nextFloat();
		}
		
		obj.velocity.x = a_velocity;
		
		float b_velocity = rn.nextInt(3) - 1;
		if(b_velocity < 0) {
			b_velocity += rn.nextFloat();
		} else {
			b_velocity -= rn.nextFloat();
		}
		
		obj.velocity.y = b_velocity;
		
		//obj.acceleration.y = -1f;
		attachObject(obj);
		
		
		/*
		 * //circles float mass = 2f; //float mass = 5f; obj = new GCircle(.5f,
		 * true, .55f,.28f,.15f); obj.inverseMass = 1 / mass; //obj.inverseMass
		 * = (float) (1f/.25); obj.inverseMomentOfInertia *= obj.inverseMass;
		 * obj.position.x = -4f; obj.position.y = 3f; obj.velocity.x = -.5f;
		 * obj.acceleration.y = -4f; attachObject(obj);
		 * 
		 * obj = new GCircle(.5f, true, .55f,.28f,.15f); obj.inverseMass = 1 /
		 * mass; //obj.inverseMass = (float) (1f/.25);
		 * obj.inverseMomentOfInertia *= obj.inverseMass; obj.position.x = 0f;
		 * obj.position.y = 3f; obj.velocity.x = 0f; obj.acceleration.y = -4f;
		 * attachObject(obj);
		 * 
		 * obj = new GCircle(.5f, true, .55f,.28f,.15f); obj.inverseMass = 1 /
		 * mass; //obj.inverseMass = (float) (1f/.25);
		 * obj.inverseMomentOfInertia *= obj.inverseMass; obj.position.x = 4f;
		 * obj.position.y = 3f; obj.velocity.x = .51f; obj.acceleration.y = -4f;
		 * attachObject(obj);
		 */

	}

	public static synchronized void playSound(final String url) {
		new Thread(new Runnable() { // the wrapper thread is unnecessary, unless
									// it blocks on the Clip finishing, see
									// comments
					public void run() {
						try {
							Clip clip = AudioSystem.getClip();
							AudioInputStream inputStream = AudioSystem
									.getAudioInputStream(GWorldMain.class
											.getResourceAsStream(url));
							clip.open(inputStream);
							clip.start();
						} catch (Exception e) {
							System.err.println(e.getMessage());
						}
					}
				}).start();
	}

	public void dropNuke() {
		nukes--;

		for (int z = 0; z < objects.size(); z++) {
			if (objects.get(z).getClass() != a_halfspace.getClass()) {
				objects.remove(z);

				sceneGraphRoot = new SceneGraphNode(false);

				sceneGraphRoot.addChild(new SceneGraphNode(false) {
					public void renderGeometry(GLAutoDrawable drawable) {
						GL2 gl = drawable.getGL().getGL2();
						gl.glColor3f(1, 1, 1);
						gl.glBegin(GL.GL_LINE_LOOP);
						gl.glVertex2f(-6, -3);
						gl.glVertex2f(6, -3);
						gl.glVertex2f(6, 3);
						gl.glVertex2f(-6, 3);

						// gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
						gl.glEnd();
					}
				});

				z = 0;
			}
		}
		playSound("bomb.wav");

		r = 0f;
		g = 1f;
		b = 0;

		the_message = "<<Player X has eradiated their asteroids. -100 eco-friendly points>>   ";
	}

	private class MyDispatcher implements KeyEventDispatcher {
		@Override
		public boolean dispatchKeyEvent(KeyEvent e) {
			if (e.getID() == KeyEvent.KEY_PRESSED) {

				if (e.getKeyCode() == KeyEvent.VK_SPACE) {
					if (nukes > 0) {
						dropNuke();
					}
				}
			}

			return false;
		}
	}
	
	private static void establishP2PCommunication(){
		
		System.out.println(" There is limited invulnerability after dieing and on first spawn.");
		System.out.println("Punishment for leaving play will soon be added, be warned cheaters.");
		System.out.println("///////////////////////////////////////////////////////////////////");
		System.out.println("                          Warning!");
		System.out.println("      Multi-player on a single pc is available but glitchy.");
		System.out.println("    Having both windows displaying at once can lead to crashes.");
		System.out.println("No crashes should occur if one window is minimized while the other");
		System.out.println("           is maximized but safety is not garunteed.");
		System.out.println("///////////////////////////////////////////////////////////////////");
		
		////////P2P implementation - prompt user, establish connection//////////////
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		boolean newNetwork = false;
		String input = null;
		while (true) {
			System.out.println("Create a new network or join an existing network (internet connection required)?");
			System.out.println("  1. Create a new network");
			System.out.println("  2. Join an existing network");
			try {
				input = in.readLine();
			} catch (IOException e) {
				continue;
			}
			if (input.equals("1")) {
				newNetwork = true;
				break;
			} else if (input.equals("2"))
				break;
		}
		long id = -1;
		while (true) {
			System.out.println("Desired network ID, 0-" + (GPeer.ID_LIMIT-1) + " [random]: ");
			try {
				input = in.readLine();
				if (input.length() == 0)
					break;
				id = Long.parseLong(input);
			} catch (NumberFormatException e) {
				continue;
			} catch (IOException e) {
				continue;
			}
			if (id >= -1 && id < GPeer.ID_LIMIT)
				break;
		}
		peer = new GPeer();
		if (newNetwork) {
			if (id == -1)
				peer.createNetwork();
			else
				peer.createNetwork(id);
		} else {
			String ip = "127.0.0.1";
			int port = GPeer.DEFAULT_SERVER_PORT;
			do {
				System.out.print("Enter host to connect to [" + ip + "[:" + port + "]]: ");
				try {
					input = in.readLine();
					int index = input.indexOf(':');
					if (index >= 0) {
						port = Integer.parseInt(input.substring(index + 1));
						input = input.substring(0, index);
					}
					if (input.length() > 0)
						ip = input;
				} catch (IOException e) {
				}
			} while (id == -1 ? !peer.connectToNetwork(ip, port) : !peer.connectToNetwork(ip, port, id));
		}
	}
	
	private static void displayNetworkOptions(){
		
		/////provide user options for viewing network status or disconnecting////	
		System.out.println("Enter \"state\" to see network info or \"disconnect\" to disconnect.");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		String input = null;
		while (true) {
			try {
				input = in.readLine();
				if (input != null && input.length() == 0)
					continue;
				Scanner sc = new Scanner(input);
				if (!sc.hasNextLong()) {
					input = sc.next();
					if (input.equals("disconnect")) {
						peer.disconnectFromNetwork();
						break;
					} else if (input.equals("state"))
						System.out.println(peer.internalState());
					else
						System.out.println("Enter \"state\" to see network info or \"disconnect\" to disconnect.");
					continue;
				}

			} catch (IOException e) {
			}
		}	
	}

}
